#define FILE_ID sshfs

#include "sshfs.h"
#include "ui.h"
#include "provider_api.h"
#include "log.h"

static int ConvertLibssh2Error(int err)
{
	switch(err) {
	case LIBSSH2_FX_NO_SUCH_FILE: return FS_ERROR_NO_FILE;
	case LIBSSH2_FX_PERMISSION_DENIED: return FS_ERROR_ACCESS_DENIED;
	case LIBSSH2_FX_FAILURE: return FS_ERROR_GENERIC_ERROR;
	case LIBSSH2_FX_FILE_ALREADY_EXISTS: return FS_ERROR_ALREADY_EXISTS;
	case LIBSSH2_FX_DIR_NOT_EMPTY: return FS_ERROR_DIR_NOT_EMPTY;
	}
	return err;
}


FilesystemProvider* CreateSshFromConfig(UserInterface *ui, const Configuration& cfg)
{
	std::list<std::string> list = cfg.LookupList("server");

	int fd = ConnectToHostList(list, "22");
	if(fd == INVALID_SOCKET)
		return NULL;

	return new Libssh2Fs(fd, cfg, ui);
}

DEFINE_FS(ssh, CreateSshFromConfig);

static void* _malloc(size_t len, void **context)
{
	return malloc(len);
}

static void _free(void *p, void **context)
{
	free(p);
}

static void* _realloc(void *p, size_t sz, void **context)
{
	return realloc(p, sz);
}


static void FillStatInfo(DirStatInfo& info, LIBSSH2_SFTP_ATTRIBUTES& attr)
{
	info.ValidityFlags = STAT_VALID_SIZE | STAT_VALID_TIMES | STAT_VALID_UNIXPERMS | STAT_VALID_UNIXOWNGRP | STAT_VALID_TYPE;
	info.FileSize = attr.filesize;
	info.Attributes = 0;
	if(LIBSSH2_SFTP_S_ISLNK(attr.permissions)) {
		info.Attributes |= STAT_FLAG_IS_SYMLINK | STAT_FLAG_IS_FILE;
	} else if(LIBSSH2_SFTP_S_ISREG(attr.permissions)) {
		info.Attributes |= STAT_FLAG_IS_FILE;
	} else if(LIBSSH2_SFTP_S_ISDIR(attr.permissions)) {
		info.Attributes |= STAT_FLAG_IS_DIRECTORY;
	} else if(LIBSSH2_SFTP_S_ISCHR(attr.permissions)) {
		info.Attributes |= STAT_FLAG_IS_CHARDEV;
	} else if(LIBSSH2_SFTP_S_ISBLK(attr.permissions)) {
		info.Attributes |= STAT_FLAG_IS_BLOCKDEV;
	} else if(LIBSSH2_SFTP_S_ISFIFO(attr.permissions)) {
		info.Attributes |= STAT_FLAG_IS_FIFO;
	} else if(LIBSSH2_SFTP_S_ISSOCK(attr.permissions)) {
		info.Attributes |= STAT_FLAG_IS_SOCKET;
	} else {
		info.Attributes |= STAT_FLAG_IS_FILE;
	}
	if((attr.permissions & (LIBSSH2_SFTP_S_IWUSR | LIBSSH2_SFTP_S_IWGRP | LIBSSH2_SFTP_S_IWOTH)) == 0) {
		info.Attributes |= STAT_FLAG_READ_ONLY;
	}
	info.CreationTime = 0;
	info.WriteTime = (uint64_t)attr.mtime * 10000000ULL;
	info.UnixGroup = 0;
	info.UnixOwner = 0;
	info.UnixPermMask = attr.permissions;
	info.Acl = NULL;
	info.AclSize = 0;
}


Libssh2Fs::Libssh2Fs(int fd, const Configuration& cfg, UserInterface *ui) : Fd(fd), Config(cfg), UI(ui), IoThread(0)
{
	Session = libssh2_session_init_ex(_malloc, _free, _realloc, this);
	libssh2_session_flag(Session, LIBSSH2_FLAG_COMPRESS, 1);
	Name = cfg.LookupSingle("name");
	Cache = new Libssh2Cache();
}

Libssh2Fs::~Libssh2Fs()
{
#ifdef WIN32
	closesocket(Fd);
#else
	close(Fd);
#endif
	Fifo.Dead();
	JoinThread(IoThread);
	delete Cache;
}

bool Libssh2Fs::CheckError(int err)
{
	return libssh2_session_last_errno(Session) == err;
}

bool Libssh2Fs::CheckSftpError(int err)
{
	return CheckError(LIBSSH2_ERROR_SFTP_PROTOCOL) && libssh2_sftp_last_error(SFTP) == err;
}

int Libssh2Fs::GetError(std::string *errstring)
{
	char *err;
	int errlen;
	int errnum = libssh2_session_last_error(Session, &err, &errlen, 0);
	if(errstring)
		errstring->assign(err, errlen);
	if(errnum == LIBSSH2_ERROR_SFTP_PROTOCOL)
		return libssh2_sftp_last_error(SFTP);
	return errnum;
}


static int gethexval(unsigned char ch)
{
	if(ch >= '0' && ch <= '9') return ch-'0';
	if(ch >= 'a' && ch <= 'f') return ch+10 - 'a';
	if(ch >= 'A' && ch <= 'F') return ch+10 - 'a';
	return -1;
}

static bool Hex2Bin(unsigned char *out, const char *in, size_t n)
{
	for(size_t i = 0; i < n; i++) {
		int h = gethexval(in[i*2]), l = gethexval(in[i*2+1]);
		if(h < 0 || l < 0) {
			return false;
		}
		out[i] = (h<<4) | l;
	}
	return true;
}

int Libssh2Fs::CheckKnownFingerprint(unsigned char *sha, unsigned char *md5)
{
	std::string authed = Config.LookupSingle("auth_sha1");
	if(!authed.empty()) {
		if(authed.size() == 40) {
			unsigned char cmp[20];
			if(Hex2Bin(cmp, authed.c_str(), 20))
				return memcmp(cmp, sha, 20) == 0 ? 1 : -1;
		}
	}
	authed = Config.LookupSingle("auth_md5");
	if(!authed.empty()) {
		if(authed.size() == 32) {
			unsigned char cmp[16];
			if(Hex2Bin(cmp, authed.c_str(), 16))
				return memcmp(cmp, md5, 16) == 0 ? 1 : -1;
		}
	}

	char hash[128];
	for(int i = 0; i < 20; i++) {
		sprintf(hash + 3*i, "%02X:", sha[i]);
	}
	hash[59] = 0; //remove the last :

	char desc[4096];
	sprintf(desc, "SHA-1 fingerprint of the server\n%s", hash);
	UserQuery query("Accept server key (yes/[no])", desc);
	UI->DoQuery(Name, &query);
	if(query.Reply != "yes")
		return -1;

	return 0;
}

int Libssh2Fs::Start()
{
	int ret;
	unsigned char hash_md5[16];
	unsigned char hash_sha1[20];

	if((ret = libssh2_session_handshake(Session, Fd)))
		return ret;

	memcpy(hash_md5, libssh2_hostkey_hash(Session, LIBSSH2_HOSTKEY_HASH_MD5), 16);
	memcpy(hash_sha1, libssh2_hostkey_hash(Session, LIBSSH2_HOSTKEY_HASH_SHA1), 20);

	ret = CheckKnownFingerprint(hash_sha1, hash_md5);
	if(ret < 0) {
		return ret;
	} else if(ret == 0) {
		//acceptable, but new
	}

	std::string username = Config.LookupSingle("username");
	if(username.empty()) {
		UserQuery query("Username");
		UI->DoQuery(Name, &query);
		if(query.Reply.empty())
			return -1;
		username = query.Reply;
	}

	//this gives a comma delimited list
	const char *authlist = libssh2_userauth_list(Session, username.c_str(), username.size());
	std::list<std::string> list;
	while(authlist) {
		const char *end = strchr(authlist, ',');
		std::string str = authlist;
		if(end) {
			str.resize(end-authlist);
			authlist = end + 1;
		} else {
			authlist = end;
		}
		list.push_back(str);
	}

	for(std::list<std::string>::iterator it = list.begin(); !libssh2_userauth_authenticated(Session) && it != list.end(); ++it) {
		if(*it == "password") {
			//do we have a password?
			std::string pwd = Config.LookupSingle("password");
			if(pwd.empty()) {
				UserQuery query("Password");
				UI->DoQuery(Name, &query);
				pwd = query.Reply;
			}
			if(!pwd.empty()) {
				libssh2_userauth_password(Session, username.c_str(), pwd.c_str());
			}
		} else if(*it == "keyboard-interactive") {
			while(libssh2_userauth_keyboard_interactive_ex(Session, username.c_str(), username.size(), KbdInteractiveAuth) == LIBSSH2_ERROR_EAGAIN) {}
		} else if(*it == "publickey") {
		}
	}
	if(!libssh2_userauth_authenticated(Session))
		return -1;

	//successfully logged in, we're up and running
	SFTP = libssh2_sftp_init(Session);
	if(!SFTP)
		return -1;

	if(!SpawnThread(SftpThread, this, &IoThread))
		return -1;

	return FS_NO_ERROR;
}

void Libssh2Fs::KbdInteractiveAuth(const char* name, int name_len, const char* instruction,
	int instruction_len, int num_prompts,
	const LIBSSH2_USERAUTH_KBDINT_PROMPT* prompts,
	LIBSSH2_USERAUTH_KBDINT_RESPONSE* responses, void **abstract)
{
	Libssh2Fs *self = (Libssh2Fs*)*abstract;

	if(num_prompts == 0)
		return;

	std::list<UserQuery*> queries;

	for(int i = 0; i < num_prompts; i++) {
		queries.push_back(new UserQuery(std::string(prompts[i].text, prompts[i].length), "Keyboard-interactive authentication"));
	}

	self->UI->DoQueries(self->Name, queries);
	int i = 0;
	for(std::list<UserQuery*>::iterator it = queries.begin(); it != queries.end(); ++it) {
		char *str = (char*)malloc((*it)->Reply.size());
		memcpy(str, (*it)->Reply.c_str(), (*it)->Reply.size());
		responses[i].text = str;
		responses[i].length = (*it)->Reply.size();
	}
}


void Libssh2Fs::Stop()
{
#ifdef WIN32
	shutdown(Fd, SD_SEND);
#endif
	Fifo.Dead();
}

void Libssh2Fs::SftpThread()
{
	SftpRequest *request;
	while((request = Fifo.DequeueWait()) != NULL) {
		switch(request->Type) {
		case SftpOpen:
			{
				LOG(LogInfo, "SFTP Open");
				LIBSSH2_SFTP_HANDLE *h;
				if(request->Open.Type != LIBSSH2_SFTP_OPENFILE && request->Open.Type != LIBSSH2_SFTP_OPENDIR) {
					//try both
					request->Open.Type = LIBSSH2_SFTP_OPENDIR;
					h = libssh2_sftp_open_ex(SFTP, request->Open.Path, request->Open.PathLen,
						request->Open.Flags, LIBSSH2_SFTP_S_IFDIR, LIBSSH2_SFTP_OPENDIR);
					if(!h) {
						request->Open.Type = LIBSSH2_SFTP_OPENFILE;
						h = libssh2_sftp_open_ex(SFTP, request->Open.Path, request->Open.PathLen,
							request->Open.Flags, request->Open.Mode, LIBSSH2_SFTP_OPENFILE);
					}
				} else {
					if(request->Open.Type == LIBSSH2_SFTP_OPENDIR) {
						//apparently the library can't create directories via open_ex?
						if((request->Open.Flags & LIBSSH2_FXF_CREAT) != 0) {
							//creating a directory
							int ret = libssh2_sftp_mkdir_ex(SFTP, request->Open.Path,
								request->Open.PathLen, request->Open.Mode);
							if(ret < 0) {
								request->Result = GetError();
								if(request->Result == LIBSSH2_FX_FILE_ALREADY_EXISTS) {
									//this might not actually be an error
									//though creating a directory on top of a file will do... i don't know
									if((request->Open.Flags & LIBSSH2_FXF_EXCL) != 0)
										break; //exclusive create
								} else {
									//failure
									break;
								}
							}
							//now, try to open the directory we just created
						}
						h = libssh2_sftp_open_ex(SFTP, request->Open.Path, request->Open.PathLen,
							LIBSSH2_FXF_READ, request->Open.Mode, request->Open.Type);
					} else {
						h = libssh2_sftp_open_ex(SFTP, request->Open.Path, request->Open.PathLen,
							request->Open.Flags, request->Open.Mode, request->Open.Type);
					}
				}
				if(!h) {
					request->Result = GetError();
				} else {
					request->Result = 0;
					request->Open.Handle = h;

					if(request->Open.AutoStat && request->Open.Type == LIBSSH2_SFTP_OPENFILE) {
						libssh2_sftp_fstat(h, &request->Open.Attr);
					}
				}
			}
			break;

		case SftpClose:
			LOG(LogInfo, "SFTP Close %p", request->Close.Handle);
			libssh2_sftp_close_handle(request->Close.Handle);
			request->Result = 0;
			break;

		case SftpWrite:
		case SftpRead:
			{
				typedef ssize_t (*IoFunc)(LIBSSH2_SFTP_HANDLE *handle, char *buffer, size_t buffer_maxlen);
				IoFunc fn = (request->Type == SftpRead) ? libssh2_sftp_read : (IoFunc)libssh2_sftp_write;

				char *buf = (char*)request->IO.Buffer;
				size_t count = request->IO.Count;
				size_t transferred = 0;
				request->Result = 0;
				while(transferred < count) {
					ssize_t retval = fn(request->IO.Handle, buf + transferred, count - transferred);
					if(retval > 0) {
						transferred += retval;
					} else {
						request->Result = retval;
						break;
					}
				}
				request->IO.Transferred = transferred;
			}
			break;

		case SftpSetStat:
			{
				int ret;
				if(request->SetStat.Handle)
					ret = libssh2_sftp_fstat_ex(request->SetStat.Handle, &request->SetStat.Attr, 1);
				else
					ret = libssh2_sftp_stat_ex(SFTP, request->SetStat.Path, strlen(request->SetStat.Path), LIBSSH2_SFTP_SETSTAT, &request->SetStat.Attr);
				request->Result = GetError();
			}
			break;

		case SftpReadDir:
			{
				char *buf = request->ReadDir.Buffer;
				char *end = buf + request->ReadDir.BufferSize;
				SftpDirEntry *next = (SftpDirEntry*)buf;
				SftpDirEntry **pnext = NULL;
				request->ReadDir.FirstEntry = (SftpDirEntry*)buf;
				request->ReadDir.Count = 0;
				int ret;
				for(;;) {
					SftpDirEntry *dirent = (SftpDirEntry*)buf;
					if(buf + sizeof(SftpDirEntry) + 8 >= end)
						break;
					dirent->Name = buf + sizeof(SftpDirEntry);
					dirent->Next = NULL;
					ret = libssh2_sftp_readdir_ex(request->ReadDir.Handle, dirent->Name, end - dirent->Name, NULL, 0, &dirent->Attrib);
					if(ret > 0) {
						request->ReadDir.Count++;
						dirent->NameLen = ret;
						if(pnext)
							*pnext = dirent;
					} else if(ret == 0 || ret == LIBSSH2_ERROR_BUFFER_TOO_SMALL) {
						break;
					} else {
						break;
					}

					buf += sizeof(SftpDirEntry) + ret;
					pnext = &dirent->Next;
				}
				request->Result = ret;
			}
			break;

		case SftpRename:
			{
				long flags = 0;
				if(request->Rename.Atomic)
					flags |= LIBSSH2_SFTP_RENAME_ATOMIC;
				if(request->Rename.Native)
					flags |= LIBSSH2_SFTP_RENAME_NATIVE;
				if(request->Rename.Overwrite)
					flags |= LIBSSH2_SFTP_RENAME_OVERWRITE;

				int ret = libssh2_sftp_rename_ex(SFTP, request->Rename.From, request->Rename.FromLen,
					request->Rename.To, request->Rename.ToLen, flags);

				request->Result = ret;
			}
			break;

		case SftpUnlink:
			if(request->Unlink.IsDir) {
				request->Result = libssh2_sftp_rmdir_ex(SFTP, request->Unlink.Path, request->Unlink.PathLen);
			} else {
				request->Result = libssh2_sftp_unlink_ex(SFTP, request->Unlink.Path, request->Unlink.PathLen);
			}
			break;
		}
		request->Evt.Signal();
	}
}

int Libssh2Fs::OpenFile(ProviderFile **f, ProviderDir **d, const std::string& path, OpenType type, bool create_ro, bool open_writable)
{
	Libssh2Fs::SftpRequest req;
	req.Type = Libssh2Fs::SftpOpen;

	{
		int err;
		ProviderCommon *obj;
		if(Cache->CacheLookup(path, obj, err, open_writable)) {
			if(err)
				return err;
			bool isdir = (obj->Dir() != NULL);
			if(isdir) {
				if(d) {
					obj->Reference();
					*d = obj->Dir();
					if(f)
						*f = NULL;
					return 0;
				}
			} else {
				if(f) {
					obj->Reference();
					*f = obj->File();
					if(d)
						*d = NULL;
					return 0;
				}
			}

			return FS_ERROR_GENERIC_ERROR;
		}
	}

	req.Open.Flags = LIBSSH2_FXF_READ;

	if(open_writable)
		req.Open.Flags |= LIBSSH2_FXF_WRITE;

	switch(type) {
	case OpenCreateNew:
		req.Open.Flags |= LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC | LIBSSH2_FXF_EXCL;
		break;

	case OpenCreateAlways:
		req.Open.Flags |= LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC;
		break;

	case OpenOpenExisting:
		//nothing to do
		break;

	case OpenOpenAlways:
		req.Open.Flags |= LIBSSH2_FXF_CREAT;
		break;

	case OpenTruncateExisting:
		//not valid under the SSH flags setup
		//so we fake it - do a normal open with WRITE access, then set the size to 0
		break;
	}


	req.Open.Mode = LIBSSH2_SFTP_S_IFREG | 
		LIBSSH2_SFTP_S_IRUSR;
	if(!create_ro)
		req.Open.Mode |= LIBSSH2_SFTP_S_IWUSR;

	req.Open.AutoStat = true;

	if(f && d) {
		req.Open.Type = LIBSSH2_SFTP_OPENEITHER;
		*f = NULL;
		*d = NULL;
	} else if(f) {
		req.Open.Type = LIBSSH2_SFTP_OPENFILE;
		*f = NULL;
	} else if(d) {
		req.Open.Type = LIBSSH2_SFTP_OPENDIR;
		*d = NULL;

		req.Open.Mode &= ~(LIBSSH2_SFTP_S_IFMT);
		req.Open.Mode |= LIBSSH2_SFTP_S_IFDIR|LIBSSH2_SFTP_S_IXUSR;
		req.Open.Flags &= ~LIBSSH2_FXF_WRITE;
	} else {
		return FS_ERROR_GENERIC_ERROR;
	}

	req.Open.Path = path.c_str();
	req.Open.PathLen = path.size();
	if(Fifo.Queue(&req))
		req.Evt.Wait();
	else
		return FS_ERROR_FS_DOWN;

	if(req.Result)
		return ConvertLibssh2Error(req.Result);

	bool isdir = (req.Open.Type == LIBSSH2_SFTP_OPENDIR);
	LIBSSH2_SFTP_HANDLE *handle = req.Open.Handle;

	if(!isdir && type == OpenTruncateExisting) {
		req.Type = SftpSetStat;
		req.SetStat.Handle = handle;
		req.SetStat.Path = NULL;
		req.SetStat.Attr.filesize = 0;
		req.SetStat.Attr.flags = LIBSSH2_SFTP_ATTR_SIZE;
		if(Fifo.Queue(&req))
			req.Evt.Wait();
		else {
			return FS_ERROR_FS_DOWN;
		}
	}


	if(isdir) {
		*d = new Libssh2Dir(path, Fifo, handle);
		Cache->CacheAdd(path, true, *d);
	} else {
		DirStatInfo stat;
		FillStatInfo(stat, req.Open.Attr);
		if(path[0] == '.')
			stat.Attributes |= STAT_FLAG_HIDDEN;

		stat.NameLen = path.size();
		stat.Name = (char*)malloc(stat.NameLen + 1);
		memcpy(stat.Name, path.c_str(), stat.NameLen+1);
		*f = new Libssh2File(path, Fifo, handle, stat);
		Cache->CacheAdd(path, false, *f);
	}

	return 0;
}

int Libssh2Fs::Rename(const std::string& from, const std::string& to, bool overwrite_existing)
{
	Libssh2Fs::SftpRequest req;
	req.Type = Libssh2Fs::SftpRename;
	req.Rename.Atomic = true;
	req.Rename.Native = true;
	req.Rename.Overwrite = overwrite_existing;
	req.Rename.From = from.c_str();
	req.Rename.FromLen = from.size();
	req.Rename.To = to.c_str();
	req.Rename.ToLen = to.size();
	if(Fifo.Queue(&req))
		req.Evt.Wait();
	else
		return FS_ERROR_FS_DOWN;
	return ConvertLibssh2Error(req.Result);
}


void Libssh2Fs::GetDiskInfo(uint64_t& free_total, uint64_t& free_quota, uint64_t& total_quota)
{
	free_total = free_quota = 1099511627776ULL;
	total_quota = 0;
}




Libssh2File::Libssh2File(const std::string& path, ProducerConsumerFifo<Libssh2Fs::SftpRequest>& fifo, LIBSSH2_SFTP_HANDLE *h, DirStatInfo& stat)
	: Fifo(fifo), Handle(h), StatInfo(stat), Path(path)
{
}

Libssh2File::~Libssh2File()
{
	Libssh2Fs::SftpRequest req;
	req.Type = Libssh2Fs::SftpClose;
	req.Close.Handle = Handle;
	if(Fifo.Queue(&req))
		req.Evt.Wait();
	free(StatInfo.Name);
	free(StatInfo.Acl);
}

int Libssh2File::Delete()
{
	Libssh2Fs::SftpRequest req;
	req.Type = Libssh2Fs::SftpUnlink;
	req.Unlink.Path = Path.c_str();
	req.Unlink.PathLen = Path.size();
	req.Unlink.IsDir = false;
	if(Fifo.Queue(&req))
		req.Evt.Wait();
	else
		return FS_ERROR_FS_DOWN;
	return ConvertLibssh2Error(req.Result);
}


const DirStatInfo& Libssh2File::Stat()
{
	return StatInfo;
}


int Libssh2File::Read(void *buffer, unsigned long long offset, unsigned long size, unsigned long& transferred)
{
	Libssh2Fs::SftpRequest req;
	req.Type = Libssh2Fs::SftpRead;
	req.IO.Handle = Handle;
	req.IO.Buffer = buffer;
	req.IO.Count = size;
	transferred = 0;
	if(Fifo.Queue(&req))
		req.Evt.Wait();
	else
		return FS_ERROR_FS_DOWN;

	transferred = req.IO.Transferred;

	return ConvertLibssh2Error(req.Result);
}

int Libssh2File::Write(void *buffer, unsigned long long offset, unsigned long size, unsigned long& transferred)
{
	Libssh2Fs::SftpRequest req;
	req.Type = Libssh2Fs::SftpWrite;
	req.IO.Handle = Handle;
	req.IO.Buffer = buffer;
	req.IO.Count = size;
	transferred = 0;
	if(Fifo.Queue(&req))
		req.Evt.Wait();
	else
		return FS_ERROR_FS_DOWN;

	transferred = req.IO.Transferred;

	return ConvertLibssh2Error(req.Result);
}

int Libssh2File::Flush()
{
	//we have no real control over the remote cache behaviour and we have no local write cache
	return FS_NO_ERROR;
}

int Libssh2File::Resize(unsigned long long size)
{
	Libssh2Fs::SftpRequest req;
	req.Type = Libssh2Fs::SftpSetStat;
	req.SetStat.Handle = Handle;
	memset(&req.SetStat.Attr, 0, sizeof(req.SetStat.Attr));
	req.SetStat.Attr.flags = LIBSSH2_SFTP_ATTR_SIZE;
	req.SetStat.Attr.filesize = size;
	if(Fifo.Queue(&req))
		req.Evt.Wait();
	else
		return FS_ERROR_FS_DOWN;
	if(req.Result == 0) {
		StatInfo.FileSize = size; //update our cache
	}
	return ConvertLibssh2Error(req.Result);
}




Libssh2Dir::Libssh2Dir(const std::string& path, ProducerConsumerFifo<Libssh2Fs::SftpRequest>& fifo, LIBSSH2_SFTP_HANDLE *h)
	: Fifo(fifo), Handle(h), DirEnts(0), Path(path)
{
	memset(&StatInfo, 0, sizeof(StatInfo));
	StatInfo.ValidityFlags = STAT_VALID_TYPE;
	StatInfo.Attributes = STAT_FLAG_IS_DIRECTORY;
}

Libssh2Dir::~Libssh2Dir()
{
	Libssh2Fs::SftpRequest req;
	req.Type = Libssh2Fs::SftpClose;
	req.Close.Handle = Handle;
	if(Fifo.Queue(&req))
		req.Evt.Wait();
	free(StatInfo.Name);
	free(StatInfo.Acl);

	if(DirEnts)
		DirEnts->Free();
}

int Libssh2Dir::Delete()
{
	Libssh2Fs::SftpRequest req;
	req.Type = Libssh2Fs::SftpUnlink;
	req.Unlink.Path = Path.c_str();
	req.Unlink.PathLen = Path.size();
	req.Unlink.IsDir = true;
	if(Fifo.Queue(&req))
		req.Evt.Wait();
	else
		return FS_ERROR_FS_DOWN;
	return ConvertLibssh2Error(req.Result);
}


const DirStatInfo& Libssh2Dir::Stat()
{
	return StatInfo;
}


void Libssh2Dir::PopulateDirEnts()
{
	size_t bufsz = 16000;
	DirEntList *tail = NULL;
	Libssh2Fs::SftpRequest req;
	req.Type = Libssh2Fs::SftpReadDir;
	req.ReadDir.Handle = Handle;
	do {
		DirEntList *next = (DirEntList*)malloc(bufsz);
		if(tail)
			tail->Next = next;
		else
			DirEnts = next;
		tail = next;
		tail->First = (Libssh2Fs::SftpDirEntry*)(tail+1);
		tail->Next = NULL;
		tail->First->Next = NULL;

		req.ReadDir.Buffer = (char*)tail->First;
		req.ReadDir.BufferSize = bufsz - 4096;
		if(Fifo.Queue(&req))
			req.Evt.Wait();
	} while(req.Result == LIBSSH2_ERROR_BUFFER_TOO_SMALL);
}

int Libssh2Dir::ListFiles(FsListFunc cb, void *context)
{
	{
		AutoCriticalSection cs(PopulateLock);
		if(!DirEnts) {
			LOG(LogInfo, "Populating dir list from server");
			PopulateDirEnts();
			if(!DirEnts)
				return 0;
		} else {
			LOG(LogInfo, "Populating dir list from cache");
		}
	}

	for(DirEntList *at = DirEnts; at; at = at->Next) {
		if(!at->First->Name)
			continue;
		for(Libssh2Fs::SftpDirEntry *e = at->First; e != NULL; e = e->Next) {
			DirStatInfo info;
			FillStatInfo(info, e->Attrib);
			if(e->Name[0] == '.') {
				info.Attributes |= STAT_FLAG_HIDDEN;
			}
			cb(e->Name, e->NameLen, info, context);
		}
	}
	return 0;
}



void Libssh2Cache::CacheAdd(const std::string& path, bool writable, ProviderCommon *obj)
{
	AutoCriticalSection cs(CacheLock);
	{
		std::map<std::string, CacheEntry>::iterator it = Cache.find(path);
		if(it != Cache.end()) //already in for some reason, oh well
			return;

		obj->Reference();
		std::pair<std::map<std::string, CacheEntry>::iterator, bool> result = Cache.insert(std::map<std::string, CacheEntry>::value_type(path, CacheEntry(obj, writable)));
		result.first->second.Self = result.first;
		GenericTimer *timer = GenericTimer::CreateOsTimer(&result.first->second);
		result.first->second.Timer = timer;
		result.first->second.Parent = this;
		timer->ScheduleTimer(90000);
	}
}

void Libssh2Cache::CacheAdd(const std::string& path, int err)
{
	AutoCriticalSection cs(CacheLock);
	{
		std::pair<std::map<std::string, CacheEntry>::iterator, bool> result = Cache.insert(std::map<std::string, CacheEntry>::value_type(path, CacheEntry(err)));
		result.first->second.Self = result.first;
		GenericTimer *timer = GenericTimer::CreateOsTimer(&result.first->second);
		result.first->second.Timer = timer;
		result.first->second.Parent = this;
		timer->ScheduleTimer(90000);
	}
}

bool Libssh2Cache::CacheLookup(const std::string& path, ProviderCommon*& obj, int& err, bool want_write)
{
	AutoCriticalSection cs(CacheLock);
	{
		std::map<std::string, CacheEntry>::iterator it = Cache.find(path);
		if(it == Cache.end())
			return false;

		CacheEntry& e = it->second;

		if(e.Obj && want_write && !e.Writable)
			return false;

		obj = e.Obj;
		err = e.Error;
		return true;
	}
}


void Libssh2Cache::CacheEntry::TimerExpired()
{
	AutoCriticalSection cs(Parent->CacheLock);
	if(Obj && !Obj->LastReference()) {
		Timer->ScheduleTimer(90000);
	} else {
		if(Obj)
			Obj->Dereference();
		Parent->Cache.erase(Self);
	}
}
